﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.StsAdmin;
using System.Diagnostics;
using System.Xml;

namespace SPVisualDev
{
    public class StsAdmCommands : ISPStsadmCommand
    {
        #region ISPStsadmCommand Members

        string ISPStsadmCommand.GetHelpMessage(string command)
        {
            return "No help is available.";
        }

        int ISPStsadmCommand.Run(string command, StringDictionary keyValues, out string output)
        {
            try
            {
                XmlDocument xmldoc = new XmlDocument();
                xmldoc.LoadXml("<XmlRoot></XmlRoot>");

                if (keyValues.ContainsKey("enumwebapplications"))
                {
                    xmldoc.DocumentElement.AppendChild(GetAllWebApplications(xmldoc));
                }
                else if (keyValues.ContainsKey("enumtoplevelsites"))
                {
                    xmldoc.DocumentElement.AppendChild(GetAllTopLevelSites(xmldoc, keyValues["type"], keyValues["url"]));
                }
                else if (keyValues.ContainsKey("enumsubwebs"))
                {
                    xmldoc.DocumentElement.AppendChild(GetAllSubWebs(xmldoc, keyValues["url"]));
                }
                else if (keyValues.ContainsKey("enumcontenttypegroups"))
                {
                    xmldoc.DocumentElement.AppendChild(GetContentTypeGroups(xmldoc, keyValues["url"]));
                }
                else if (keyValues.ContainsKey("enumcontenttypesbygroup"))
                {
                    xmldoc.DocumentElement.AppendChild(GetContentTypesByGroup(xmldoc, keyValues["url"], keyValues["group"]));
                }
                else if (keyValues.ContainsKey("getcontenttypedata"))
                {
                    xmldoc.DocumentElement.AppendChild(GetContentTypesData(xmldoc, keyValues["url"], keyValues["name"]));
                }
                else if (keyValues.ContainsKey("getallbuiltincontenttypes"))
                {
                    xmldoc.DocumentElement.AppendChild(GetAllBuiltInContenTypes(xmldoc));
                }
                else
                {
                    throw new Exception("No valid command supplied.");
                }

                output = xmldoc.OuterXml;

                return 0;
            }
            catch(Exception ex)
            {
                output = "<XmlRoot><Error>" + Common.FilterXmlString(ex.Message) + "</Error><StackTrace>" + Common.FilterXmlString(ex.StackTrace) + "</StackTrace></XmlRoot>";
                return 1;
            }
        }

        #endregion

        private XmlNode GetAllWebApplications(XmlDocument OwnerDocument)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("WebApplications");

            foreach (SPWebApplication webapp in SPWebService.ContentService.WebApplications)
            {
                XmlElement el = OwnerDocument.CreateElement("WebApplication");
                el.SetAttribute("Name", webapp.Name);
                el.SetAttribute("Url", webapp.GetResponseUri(SPUrlZone.Default).ToString());
                el.SetAttribute("DisplayName", webapp.DisplayName);
                el.SetAttribute("DefaultServerComment", webapp.DefaultServerComment);
                el.SetAttribute("Id", webapp.Id.ToString());
                el.SetAttribute("Type", "Content");
                parent_node.AppendChild(el);
            }

            foreach (SPWebApplication webapp in SPWebService.AdministrationService.WebApplications)
            {
                XmlElement el = OwnerDocument.CreateElement("WebApplication");
                el.SetAttribute("Name", webapp.Name);
                el.SetAttribute("Url", webapp.GetResponseUri(SPUrlZone.Default).ToString());
                el.SetAttribute("DisplayName", webapp.DisplayName);
                el.SetAttribute("DefaultServerComment", webapp.DefaultServerComment);
                el.SetAttribute("Id", webapp.Id.ToString());
                el.SetAttribute("Type", "Admin");
                parent_node.AppendChild(el);
            }

            return parent_node;
        }

        private XmlNode GetAllTopLevelSites(XmlDocument OwnerDocument, string Type, string Url)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("Sites");

            SPWebApplication webapp = SPWebApplication.Lookup(new Uri(Url));
            foreach (SPSite site in webapp.Sites)
            {
                try
                {
                    XmlElement el = OwnerDocument.CreateElement("Site");
                    el.SetAttribute("Url", site.Url);
                    el.SetAttribute("Title", site.RootWeb.Title);
                    parent_node.AppendChild(el);
                }
                finally
                {
                    site.Close();
                }
            }

            return parent_node;
        }

        private XmlNode GetAllSubWebs(XmlDocument OwnerDocument, string Url)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("Webs");

            using (SPSite site = new SPSite(Url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPWebCollection coll = web.Webs;
                    foreach (SPWeb w in coll)
                    {
                        try
                        {
                            if (w.IsRootWeb || w.ID == web.ID)
                                continue;

                            XmlElement el = OwnerDocument.CreateElement("Web");
                            el.SetAttribute("Url", w.Url);
                            el.SetAttribute("Title", w.Title);
                            parent_node.AppendChild(el);
                        }
                        finally
                        {
                            w.Close();
                        }
                    }
                }
            }

            return parent_node;
        }

        private XmlNode GetContentTypeGroups(XmlDocument OwnerDocument, string Url)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("ContentTypeGroups");

            using (SPSite site = new SPSite(Url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPContentTypeCollection coll = web.ContentTypes;
                    List<string> list = new List<string>();

                    foreach (SPContentType ct in coll)
                    {
                        if (!list.Contains(ct.Group))
                            list.Add(ct.Group);
                    }

                    foreach (string s in list)
                    {
                        XmlElement el = OwnerDocument.CreateElement("Group");
                        el.SetAttribute("Name", s);
                        parent_node.AppendChild(el);
                    }
                }
            }

            return parent_node;
        }

        private XmlNode GetContentTypesByGroup(XmlDocument OwnerDocument, string Url, string Group)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("ContentTypes");

            using (SPSite site = new SPSite(Url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPContentTypeCollection coll = web.ContentTypes;
                    List<SPContentType> list = new List<SPContentType>();

                    foreach (SPContentType ct in coll)
                    {
                        if (ct.Group == Group && !list.Contains(ct))
                            list.Add(ct);
                    }

                    foreach (SPContentType ct in list)
                    {
                        XmlElement el = OwnerDocument.CreateElement("ContentType");
                        el.SetAttribute("Id", ct.Id.ToString());
                        el.SetAttribute("Name", ct.Name);
                        parent_node.AppendChild(el);
                    }
                }
            }

            return parent_node;
        }

        private XmlNode GetContentTypesData(XmlDocument OwnerDocument, string Url, string Name)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("ContentTypeData");

            using (SPSite site = new SPSite(Url))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    SPContentType ct = web.ContentTypes[Name];
                    XmlElement ImportedXml = OwnerDocument.CreateElement("ContentType", Common.C_MS_SHAREPOINT_XMLNS);

                    ImportedXml.SetAttribute("ID", ct.Id.ToString());
                    ImportedXml.SetAttribute("Name", ct.Name);
                    ImportedXml.SetAttribute("Description", ct.Description);
                    ImportedXml.SetAttribute("Group", ct.Group);

                    SPContentType ct_parent = ct.Parent;
                    Hashtable remove_fields = new Hashtable();

                    if (ct_parent != null)
                    {
                        while (ct_parent.Name != "System")
                        {
                            foreach (SPField fld in ct_parent.Fields)
                                if (!remove_fields.ContainsKey(fld.Id))
                                    remove_fields.Add(fld.Id, true);

                            ct_parent = ct_parent.Parent;

                        }
                    }

                    List<XmlElement> fields_to_add = new List<XmlElement>();
                    XmlElement xe_fieldrefs = OwnerDocument.CreateElement("FieldRefs", Common.C_MS_SHAREPOINT_XMLNS);
                    
                    foreach (SPField fld in ct.Fields)
                    {
                        if (remove_fields.ContainsKey(fld.Id))
                            continue;

                        XmlElement xe_fieldref = OwnerDocument.CreateElement("FieldRef", Common.C_MS_SHAREPOINT_XMLNS);
                        xe_fieldref.SetAttribute("ID", fld.Id.ToString());
                        xe_fieldref.SetAttribute("Name", fld.InternalName);

                        xe_fieldrefs.AppendChild(xe_fieldref);

                        XmlDocument field_schema_xml = new XmlDocument();
                        field_schema_xml.LoadXml(fld.SchemaXml);

                        XmlElement new_field_xml = OwnerDocument.CreateElement("Field", Common.C_MS_SHAREPOINT_XMLNS);
                        foreach (XmlAttribute attr in field_schema_xml.DocumentElement.Attributes)
                        {
                            if (attr.Name == "Customization" ||
                                attr.Name == "Version")
                                continue;

                            new_field_xml.SetAttribute(attr.Name, attr.Value);
                        }

                        fields_to_add.Add(new_field_xml);

                    }

                    ImportedXml.AppendChild(xe_fieldrefs);

                    parent_node.AppendChild(ImportedXml);



                    XmlElement el = OwnerDocument.CreateElement("Fields");
                    foreach (XmlElement el2 in fields_to_add)
                    {
                        el.AppendChild(el2);
                    }

                    parent_node.AppendChild(el);

                }
            }

            return parent_node;
        }

        private XmlNode GetAllBuiltInContenTypes(XmlDocument OwnerDocument)
        {
            XmlElement parent_node = OwnerDocument.CreateElement("ContentTypes");

            SPFeatureDefinition def = SPFarm.Local.FeatureDefinitions["ctypes"];
            foreach (SPElementDefinition element in def.GetElementDefinitions(System.Globalization.CultureInfo.CurrentCulture))
            {
                if (element.ElementType == "ContentType")
                {
                    XmlElement el = OwnerDocument.CreateElement("Group");
                    el.SetAttribute("Name", Common.GetXmlAttribute(element.XmlDefinition as XmlElement, "Name"));
                    el.SetAttribute("Id", Common.GetXmlAttribute(element.XmlDefinition as XmlElement, "ID"));
                    parent_node.AppendChild(el);
                }
            }


            return parent_node;
        }

        internal static int ExecuteStsadmCommand(string cmd, string arguments, out string output)
        {
            using (System.Diagnostics.Process proc = new Process())
            {
                proc.StartInfo.Arguments = "-o spvisualdev.commands -" + cmd + " " + arguments;
                proc.StartInfo.FileName = Common.GetSharePointInstallPath() + @"\BIN\stsadm.exe";

                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.StartInfo.CreateNoWindow = true;

                //proc.ErrorDataReceived += new DataReceivedEventHandler(proc_DataReceived);
                //proc.OutputDataReceived += new DataReceivedEventHandler(proc_DataReceived);

                proc.Start();

                //proc.BeginOutputReadLine();
                //proc.BeginErrorReadLine();

                /*
                while (!proc.HasExited)
                {
                    System.Threading.Thread.Sleep(300);
                }
                */

                output = proc.StandardOutput.ReadToEnd();
                if (proc.ExitCode != 0)
                {
                    string err = output;

                    if (!err.StartsWith("<XmlRoot><Error>"))
                    {
                        XmlDocument xmldoc = new XmlDocument();
                        xmldoc.LoadXml(err);

                        throw new Exception(xmldoc.DocumentElement.SelectSingleNode("Error").InnerText + "\n" + xmldoc.DocumentElement.SelectSingleNode("StackTrace").InnerText);
                    }
                    else
                    {
                        throw new Exception("Unspecified error occured. \"" + err + "\nExit code: " + proc.ExitCode.ToString());
                    }
                }

                return proc.ExitCode;
            }
        }
    }

    public class StsAdmWrapperException : Exception
    {
        public StsAdmWrapperException(string Message) : base(Message) { }

        public int ExitCode { get; set; }

    }
}
